﻿namespace HuwmanCode.ServiceProcess

open System
open System.Diagnostics
open System.ServiceProcess
open HuwmanCode

type ServiceDependency =
| ServiceName of String
| ServiceType of Type

///<summary>
/// Specifies the WindowsService dependency on another service
///</summary>
[<AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)>]
type public DependsOnAttribute(dependency:ServiceDependency) =
    inherit Attribute()

    new (serviceName) = new DependsOnAttribute(ServiceName(serviceName))
    new (serviceType) = new DependsOnAttribute(ServiceType(serviceType))
    
    member internal x.Dependency = dependency

    static member internal GetDependencies(instance) =
        let dependencies = 
            instance.GetType().GetCustomAttributes(typeof<DependsOnAttribute>, false)
            |> Array.map(fun o -> o :?> DependsOnAttribute)
            |> Array.map(fun a -> a.Dependency)
        dependencies

///<summary>
/// Specfies the WindowsService service account details
///</summary>
[<AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)>]
type public ServiceAccountAttribute(serviceAccount : ServiceAccount, userName : string) =
    inherit Attribute()
    new (serviceAccount : ServiceAccount) = ServiceAccountAttribute(serviceAccount, String.Empty)
    
    member internal x.Account
        with get() = serviceAccount

     member internal x.UserName
        with get() = userName

///<summary>
/// Specifies that the WindowsService should be started after installation
///</summary>
[<AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)>]
type public StartAfterInstallAttribute(perform : bool) =
    inherit Attribute()
    new () = StartAfterInstallAttribute(true)
    
    member internal x.Perform
        with get() = perform

    static member internal IsDecorated(instance) =
        instance.GetType().GetCustomAttributes(typeof<StartAfterInstallAttribute>, true)
        |> Array.map(fun o -> o :?> StartAfterInstallAttribute)
        |> Array.tryFind(fun a -> a.Perform)
        |> Option.isSome

///<summary>
/// Specifies failure recovery options for a Windows Service.
///</summary>
[<AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)>]
type RecoverFromFailuresAttribute(actions, ?resetFailCountAfter, ?rebootMessage) =
    inherit Attribute()

    new (firstFailure) = RecoverFromFailuresAttribute([| new ServiceFailureAction(firstFailure, 500u) |])
    new (firstFailure, firstFailureDelay) = RecoverFromFailuresAttribute([| new ServiceFailureAction(firstFailure, firstFailureDelay) |])
    new (firstFailure, firstFailureDelay, resetFailCountAfter) = RecoverFromFailuresAttribute([| new ServiceFailureAction(firstFailure, firstFailureDelay) |], resetFailCountAfter)
    new (firstFailure, firstFailureDelay, resetFailCountAfter, rebootMessage) = RecoverFromFailuresAttribute([| new ServiceFailureAction(firstFailure, firstFailureDelay) |], resetFailCountAfter, rebootMessage)

    new (firstFailure:FailureRecoveryAction, secondFailure) = 
        RecoverFromFailuresAttribute([| new ServiceFailureAction(firstFailure, 500u);new ServiceFailureAction(secondFailure, 500u) |])
    new (firstFailure:FailureRecoveryAction, firstFailureDelay, secondFailure, secondFailureDelay) = 
        RecoverFromFailuresAttribute([| new ServiceFailureAction(firstFailure, firstFailureDelay);new ServiceFailureAction(secondFailure, secondFailureDelay) |])
    new (firstFailure:FailureRecoveryAction, firstFailureDelay, secondFailure, secondFailureDelay, resetFailCountAfter) = 
        RecoverFromFailuresAttribute([| new ServiceFailureAction(firstFailure, firstFailureDelay);new ServiceFailureAction(secondFailure, secondFailureDelay) |], resetFailCountAfter)
    new (firstFailure:FailureRecoveryAction, firstFailureDelay, secondFailure, secondFailureDelay, resetFailCountAfter, rebootMessage) = 
        RecoverFromFailuresAttribute([| new ServiceFailureAction(firstFailure, firstFailureDelay);new ServiceFailureAction(secondFailure, secondFailureDelay) |], resetFailCountAfter, rebootMessage)

    new (firstFailure:FailureRecoveryAction, secondFailure, thirdFailure) = 
        RecoverFromFailuresAttribute([| new ServiceFailureAction(firstFailure, 500u);new ServiceFailureAction(secondFailure, 500u);new ServiceFailureAction(thirdFailure, 500u) |])
    new (firstFailure:FailureRecoveryAction, firstFailureDelay, secondFailure, secondFailureDelay, thirdFailure, thirdFailureDelay) = 
        RecoverFromFailuresAttribute([| new ServiceFailureAction(firstFailure, firstFailureDelay);new ServiceFailureAction(secondFailure, secondFailureDelay);new ServiceFailureAction(thirdFailure, thirdFailureDelay) |])
    new (firstFailure:FailureRecoveryAction, firstFailureDelay, secondFailure, secondFailureDelay, thirdFailure, thirdFailureDelay, resetFailCountAfter) = 
        RecoverFromFailuresAttribute([| new ServiceFailureAction(firstFailure, firstFailureDelay);new ServiceFailureAction(secondFailure, secondFailureDelay);new ServiceFailureAction(thirdFailure, thirdFailureDelay) |], resetFailCountAfter)
    new (firstFailure:FailureRecoveryAction, firstFailureDelay, secondFailure, secondFailureDelay, thirdFailure, thirdFailureDelay, resetFailCountAfter, rebootMessage) = 
        RecoverFromFailuresAttribute([| new ServiceFailureAction(firstFailure, firstFailureDelay);new ServiceFailureAction(secondFailure, secondFailureDelay);new ServiceFailureAction(thirdFailure, thirdFailureDelay) |], resetFailCountAfter, rebootMessage)

    member internal this.Actions = actions 
    member internal this.RebootMessage = rebootMessage |> Option.defaultTo("Service failed, system restarting!")
    member internal this.ResetFailCountAfter = resetFailCountAfter |> Option.defaultTo(60 * 60 * 24) // 1day

    static member internal GetRecoveryOptions(instance) =
        instance.GetType().GetCustomAttributes(typeof<RecoverFromFailuresAttribute>, false)
        |> Array.map(fun o -> o :?> RecoverFromFailuresAttribute)
        |> Array.nthOrNone 0